package roborally.itemsonboard;

import roborally.board.Board;
import roborally.supporting.PhysicalAmount;
import roborally.supporting.Position;
import roborally.supporting.Unit;
import be.kuleuven.cs.som.annotate.*;

/**
 * A class representing a item with an amount of energy. The item has an energy limit and an energy
 * level. The energyItem can also return the fractional energy rate that he possesses.
 * 
 * @invar The boardItem must have a valid position at all times. (repeated)
 * 		| canHaveAsPosition(this.getPosition())
 * @invar The boardItem must have a proper board at all times. (repeated)
 * 		| hasProperBoard()
 * 
 * @invar The energyItem must have a valid energy level at all times.
 * 		| canHaveAsEnergyLevel(this.getEnergyLevel())
 * @invar The energyItem must have a valid energy limit at all times.
 * 		| canHaveAsEnergyLimit(this.getEnergyLimit())
 * 
 * @author Molderez Tom, 2de bach Ingenieurswetenschappen Computerwetenschappen-Elektrotechniek and Reniers Jorn, 2de bach Ingenieurswetenschappen Werktuigkunde-Computerwetenschappen
 * @version 3.0
 */
public abstract class EnergyItem extends BoardItemImpl {
	
	/**
	 * Creates a new item that has energy.
	 * 
	 * @param energyLimit The energyLimit of the new item.
	 * @param energyLevel The energyLevel of the new item.
	 * @param board The board of the new item.
	 * @param position The position of the new item.
	 * @pre The energyLimit must be possible.
	 * 		| isPossibleEnergyLimit(energyLimit)
	 * @pre The energyLevel must be positive and smaller than or equal to energyLimit.
	 * 		| (new this).canHaveAsEnergyLevel(energyLevel)
	 * @effect The board and position of this item are set to the given values.
	 * 		| super(board, position)
	 * @post The energyLimit is set to the given value.
	 * 		| (new this).getEnergyLimit() == energyLimit
	 * @throws IllegalArgumentException (repeated)
	 * 		Position is null
	 * 		| position== null
	 * @throws IllegalArgumentException (repeated)
	 * 		When the board is not null and can not contain this item.
	 * 		| board != null && !board.canHaveAsItem(new this)
	 */
	@Model
	protected EnergyItem(PhysicalAmount energyLimit, PhysicalAmount energyLevel, Board board, Position position) 
			throws IllegalArgumentException {
		super(board, position);
		assert(isPossibleEnergyLimit(energyLimit));
		this.energyLimit = energyLimit;
		this.setEnergyLevel(energyLevel); // precondition checked here.
	}
	
	/**
	 * Returns the energy level of this item.
	 */
	@Basic @Raw
	public PhysicalAmount getEnergyLevel() {
		return this.energyLevel;
	}
	
	/**
	 * Return the energy level of this item as a fraction of the maximum amount.
	 * 
	 * @return a number indicating the energy level as a fraction of the maximum amount.
	 * 			If the maximum amount happens to be zero, the result is not a number.
	 * 		| if this.getEnergyLimit().getAmount() != 0
	 * 		| 	then result == this.getEnergyLimit().getAsFractionOfThis(this.getEnergyLevel())
	 * 		| else
	 * 		|	result == Double.NaN
	 */
	@Raw
	public double getFractialEnergyLevel() {
		if(this.getEnergyLimit().getAmount() != 0)
			return this.getEnergyLimit().getAsFractionOfThis(this.getEnergyLevel());
		else return Double.NaN;
	}
	
	/**
	 * Sets the energyLevel of this item to the given value.
	 * 
	 * @param energyLevel the energy level that the item will get.
	 * @pre	The value of energyLevel must be valid.
	 * 		| canHaveAsEnergyLevel(energyLevel)
	 * @post The energy level of the item is equal to the given value.
	 * 		| (this new).getEnergyLevel == energyLevel
	 */
	@Raw
	public void setEnergyLevel(PhysicalAmount energyLevel) {
		assert(canHaveAsEnergyLevel(energyLevel));
		this.energyLevel = energyLevel;
	}
	
	/**
	 * Controls whether the energy level is valid.
	 * 
	 * @param energyLevel the energy level to be controlled.
	 * @return False if the energyLevel is null
	 * 		| if energyLevel == null
	 * 		|	then result == false
	 * @return False if the amount is negative
	 * 		| if energyLevel.getAmount() < 0
	 * 			then result == false
	 * @return False if the energyUnit can not be converted to Unit.WATTSECOND.
	 * 		| if !energyLevel.getUnit().canConvertTo(Unit.WATTSECOND)
	 * 		|	then result == false
	 * @return True if the energyLevel of this is smaller or equal to the energyLimit.
	 * 		| if this.getEnergyLimit().compareTo(energyLevel) == 1 || this.getEnergyLimit().compareTo(energyLevel) == 0
	 * 		| 	then result == true
	 */
	@Raw
	public boolean canHaveAsEnergyLevel(PhysicalAmount energyLevel){
		if(energyLevel == null)
			return false;
		if(energyLevel.getAmount()<0)
			return false;
		if(!energyLevel.getUnit().canConvertTo(Unit.WATTSECOND))
			return false;
		int temp = this.getEnergyLimit().compareTo(energyLevel); 
		if (temp == 1 || temp == 0)
			return true;
		return false;
		
	}
	
	//The energy level of the item.
	private PhysicalAmount energyLevel;
	
	/**
	 * returns the maximum amount of energy.
	 */
	@Basic @Raw
	public PhysicalAmount getEnergyLimit() {
		return this.energyLimit;
	}
	
	/**
	 * Sets the energyLimit of this to the given value.
	 * 
	 * @param energyLimit The energyLimit to be set.
	 * @pre The energyLimit must be valid.
	 * 		| canHaveAsEnergyLimit(energyLimit)
	 * @post This will have the energyLimit as energyLimit.
	 * 		| (this new).getEnergyLimit == energyLimit
	 */
	@Raw
	public void setEnergyLimit(PhysicalAmount energyLimit) {
		assert canHaveAsEnergyLimit(energyLimit);
		this.energyLimit = energyLimit;
	}
	
	/**
	 * Controls if this can have the given energyLimit.
	 * 
	 * @param energyLimit The energyLimit to be controlled.
	 * @return False if the energyLimit not possible.
	 * 		| if !isPossibleEnergyLimit(energyLimit)
	 * 		|	then result == false
	 * @return False if the energyLimit is smaller then the current energyLevel.
	 * 		| if energyLimit.compareTo(this.getEnergyLevel()) == -1
	 * 		|	then result == false
	 */
	@Raw
	public boolean canHaveAsEnergyLimit(PhysicalAmount energyLimit) {
		if(!isPossibleEnergyLimit(energyLimit))
			return false;
		else if(energyLimit.compareTo(this.getEnergyLevel()) == -1)
			return false;
		else 
			return true;
	}
	
	/**
	 * Controls whether energyLimit is possible.
	 * 
	 * @param energyLimit The energyLimit to be controlled.
	 * @return False if the energyLimit is null.
	 * 		| if energyLimit == null
	 * 		|	then result == false
	 * @return False if the units can not convert to Unit.WATTSECOND
	 * 		| if !energyLimit.getUnit().canConvertTo(Unit.WATTSECOND)
	 * 		|	then result == false
	 * @return False if the energyLimit is negative.
	 * 		| if energyLimit.getAmount() < 0
	 * 		|	then result == false
	 */
	@Raw
	public boolean isPossibleEnergyLimit(PhysicalAmount energyLimit) {
		if(energyLimit == null)
			return false;
		if(!energyLimit.getUnit().canConvertTo(Unit.WATTSECOND))
			return false;
		else if(energyLimit.getAmount() < 0)
			return false;
		return true;
	}

	
	/**
	 * The energy limit of the item.
	 */
	private PhysicalAmount energyLimit;
	
	/**
	 * Returns a textual specification of this EnergyItem.
	 * 
	 * @return A textual specification of this EnergyItem.
	 * 		| result ==super.toString() + ", [Energylevel: " + this.getEnergyLevel().toString()+ "], [EnergyLimit: "+ this.getEnergyLimit() +"]"
	 */
	@Override
	public String toString() {
		return super.toString() + ", [Energylevel: " + this.getEnergyLevel().toString()+ "], [EnergyLimit: "+ this.getEnergyLimit() +"]";
	}
	
	/**
	 * Controls if the energyItem satisfies all its class invariants.
	 * 
	 * 
	 * @return False if the board is not valid. (repeated)
	 * 		| if !hasProperBoard()
	 * 		|	then result == false
	 * @return False if the position is not legal. (repeated)
	 * 		| if !canHaveAsPosition(this.getPosition())
	 * 		|	then result == false
	 * @return False if the energyLevel is not valid.
	 * 		| if !canHaveAsEnergyLevel(this.getEnergyLevel()
	 * 		|	then result == false
	 * @return False if the energyLimit is not valid.
	 * 		| if !canHaveAsEnergyLimit(this.getEnergyLimit())
	 * 		|	then result == false
	 */	
	@Override
	@Raw
	public boolean legalStatus() {
		return super.legalStatus() && this.canHaveAsEnergyLevel(this.getEnergyLevel()) &&
				this.canHaveAsEnergyLimit(this.getEnergyLimit());
	}
}
